<?php

/**
 * Upload handler : Handles $_FILES and checks for security
 *
 * Example Usage:
 * <code>
 * $upload = new classUpload();
 * $upload->out_file_dir     = './uploads';
 * $upload->max_file_size    = '10000000';
 * $upload->make_script_safe = 1;
 * $upload->allowed_file_ext = array( 'gif', 'jpg', 'jpeg', 'png' );
 * $upload->process();
 *
 * if ( $upload->error_no )
 * {
 * 	  switch( $upload->error_no )
 * 	  {
 * 		  case 1:
 * 			  print "没有文件被上传"; exit();
 * 		  case 2:
 * 		  case 5:
 * 			   print "该类型文件不允许被上传"; exit();
 * 		  case 3:
 * 			   print "文件大小超出限制"; exit();
 *         case 4:
 * 			   print "本地文件系统读写权限出错"; exit();
 * 	  }
 *  }
 * print $upload->saved_upload_name . " uploaded!";
 * </code>
 * ERRORS:
 * 1: 没有文件被上传
 * 2: 该类型文件不允许被上传
 * 3: 文件大小超出限制
 * 4: 本地文件系统读写权限出错
 * 5: 伪装的图片文件 ( XSS 攻击？)
 *
 */
class cb_upload {

    /**
     * 表单字段名称
     */
    public $upload_form_field = 'file';
    /**
     * 输出文件名，不包括扩展名（留空则使用原有文件名）
     */
    public $out_file_name = '';
    /**
     * 文件输出目录，不包括尾部的"/"
     */
    public $out_file_dir = './';
    /**
     * 允许的上传文件大小，单位字节
     */
    public $max_file_size = 8388608;
    /**
     * 强制将 PHP, CGI, 等脚本文件转换为文本文件
     */
    public $make_script_safe = 1;
    /**
     * 强制指定非图片文件扩展名 (如不需要，请留空)
     */
    public $force_data_ext = '';
    /**
     * 允许上传的文件类型 array( 'gif', 'jpg', 'jpeg'..)
     */
    public $allowed_file_ext = array();
    /**
     * 是否允许检查扩展名
     */
    public $check_file_ext = true;
    /**
     * 图片扩展名数组
     */
    public $image_ext = array('gif', 'jpeg', 'jpg', 'jpe', 'png');
    /**
     * 防止伪造图片扩展名
     */
    public $image_check = true;
    /**
     * 返回当前文件扩展名
     */
    public $file_extension = '';
    /**
     * 如果 force_data_ext == 1, 这个值将保存真实的扩展名
     * $file_extension将保存  'force_data_ext'
     */
    public $real_file_extension = '';
    /**
     * 返回错误代码
     */
    public $error_no = 0;
    /**
     * 返回上传的文件是否为图片
     */
    public $is_image = 0;
    /**
     * 返回原始文件名
     */
    public $original_file_name = "";
    /**
     * 返回最终被保存在硬盘上的文件名，不包括路径信息
     */
    public $parsed_file_name = "";
    /**
     * 返回最终的文件名，包括路径信息
     */
    public $saved_upload_name = "";

    /**
     * 处理上传
     */
    public function process() {
        $this->_cleanPaths();

        if (!function_exists('getimagesize')) {
            $this->image_check = 0;
        }

        $FILE_NAME = isset($_FILES[$this->upload_form_field]['name']) ? $_FILES[$this->upload_form_field]['name'] : '';
        $FILE_SIZE = isset($_FILES[$this->upload_form_field]['size']) ? $_FILES[$this->upload_form_field]['size'] : '';
        $FILE_TYPE = isset($_FILES[$this->upload_form_field]['type']) ? $_FILES[$this->upload_form_field]['type'] : '';

        $FILE_TYPE = preg_replace("/^(.+?);.*$/", "\\1", $FILE_TYPE);

        if (!isset($_FILES[$this->upload_form_field]['name'])
                or $_FILES[$this->upload_form_field]['name'] == ""
                or !$_FILES[$this->upload_form_field]['name']
                or !$_FILES[$this->upload_form_field]['size']
                or ($_FILES[$this->upload_form_field]['name'] == "none")
        ) {
            if ($_FILES[$this->upload_form_field]['error'] == 2) {
                $this->error_no = 3;
            } else {
                $this->error_no = 1;
            }

            return false;
        }

        if (!is_uploaded_file($_FILES[$this->upload_form_field]['tmp_name'])) {
            $this->error_no = 1;
            return false;
        }

        if ($this->check_file_ext) {
            if (!is_array($this->allowed_file_ext) or !count($this->allowed_file_ext)) {
                $this->error_no = 2;
                return false;
            }
        }

        $this->file_extension = $this->_getFileExtension($FILE_NAME);

        if (!$this->file_extension) {
            $this->error_no = 2;
            return false;
        }

        $this->real_file_extension = $this->file_extension;

        if ($this->check_file_ext AND !in_array($this->file_extension, $this->allowed_file_ext)) {
            $this->error_no = 2;
            return false;
        }

        if (( $this->max_file_size ) and ( $FILE_SIZE > $this->max_file_size )) {
            $this->error_no = 3;
            return false;
        }

        $this->original_file_name = $FILE_NAME;

        $FILE_NAME = preg_replace("/[^\w\.]/", "_", $FILE_NAME);

        //-------------------------------------------------
        // 去扩展名
        //-------------------------------------------------
        if ($this->out_file_name) {
            $this->parsed_file_name = $this->out_file_name;
        } else {
            $this->parsed_file_name = str_replace('.' . $this->file_extension, "", $FILE_NAME);
        }

        $renamed = 0;

        if ($this->make_script_safe) {
            if (preg_match("/\.(cgi|pl|js|asp|php|html|htm|jsp|jar)(\.|$)/i", $FILE_NAME)) {
                $FILE_TYPE = 'text/plain';
                $this->file_extension = 'txt';
                $this->parsed_file_name = preg_replace("/\.(cgi|pl|js|asp|php|html|htm|jsp|jar)(\.|$)/i", "$2", $this->parsed_file_name);

                $renamed = 1;
            }
        }

        if (is_array($this->image_ext) and count($this->image_ext)) {
            if (in_array($this->real_file_extension, $this->image_ext)) {
                $this->is_image = 1;
            }
        }

        //-------------------------------------------------
        // 重新添扩展名
        //-------------------------------------------------

        if ($this->force_data_ext and !$this->is_image) {
            $this->file_extension = str_replace(".", "", $this->force_data_ext);
        }

        $this->parsed_file_name .= '.' . $this->file_extension;

        $this->saved_upload_name = $this->out_file_dir . '/' . $this->parsed_file_name;

        if (!@move_uploaded_file($_FILES[$this->upload_form_field]['tmp_name'], $this->saved_upload_name)) {
            $this->error_no = 4;
            return;
        } else {
            @chmod($this->saved_upload_name, 0777);
        }

        if (!$renamed AND $this->file_extension != 'txt') {
            $this->_checkXSSInfile();

            if ($this->error_no) {
                return false;
            }
        }

        if ($this->is_image) {

            if ($this->image_check) {
                $img_attributes = @getimagesize($this->saved_upload_name);

                if (!is_array($img_attributes) or !count($img_attributes)) {
                    @unlink($this->saved_upload_name);
                    $this->error_no = 5;
                    return false;
                } else if (!$img_attributes[2]) {
                    @unlink($this->saved_upload_name);
                    $this->error_no = 5;
                    return false;
                } else if ($img_attributes[2] == 1 AND ( $this->file_extension == 'jpg' OR $this->file_extension == 'jpeg' )) {
                    // Potential XSS attack with a fake GIF header in a JPEG
                    @unlink($this->saved_upload_name);
                    $this->error_no = 5;
                    return false;
                }
            }
        }

        if (filesize($this->saved_upload_name) != $_FILES[$this->upload_form_field]['size']) {
            @unlink($this->saved_upload_name);

            $this->error_no = 1;
            return false;
        }
    }

    /**
     * 检查 XSS.  如果查实则删除文件, 将 error_no 设置为 5 并且返回
     */
    private function _checkXSSInfile() {
        // HTML added inside an inline file is not good in IE...

        $fh = fopen($this->saved_upload_name, 'rb');

        $file_check = fread($fh, 512);

        fclose($fh);

        if (!$file_check) {
            @unlink($this->saved_upload_name);
            $this->error_no = 5;
            return false;
        } else if (preg_match("#<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy#si", $file_check)) {
            @unlink($this->saved_upload_name);
            $this->error_no = 5;
            return false;
        }
    }

    /**
     * 返回文件扩展名
     */
    public function _getFileExtension($file) {
        return strtolower(str_replace(".", "", substr($file, strrpos($file, '.'))));
    }

    /**
     * 将路径尾部的“/”去除,检查并生成目录
     */
    private function _cleanPaths() {
        $this->out_file_dir = rtrim($this->out_file_dir, '/');
        $this->_mkdirs($this->out_file_dir);
    }

    /**
     * 按指定路径生成目录
     *
     * @param string $path 路径
     */
    private function _mkdirs($path) {
        $adir = explode('/', $path);
        $dirlist = '';
        $rootdir = array_shift($adir);
        if (($rootdir != '.' || $rootdir != '..') && !file_exists($rootdir)) {
            @mkdir($rootdir);
        }
        foreach ($adir as $key => $val) {
            if ($val != '.' && $val != '..') {
                $dirlist .= "/" . $val;
                $dirpath = $rootdir . $dirlist;
                if (!file_exists($dirpath)) {
                    @mkdir($dirpath);
                    @chmod($dirpath, 0777);
                }
            }
        }
    }

}
